"""
Sound and music management system for tank battle game
"""
import pygame
import os
from typing import Dict, Optional
from .config import SOUNDS_DIR
from .procedural_sounds import get_procedural_sound_generator


class SoundManager:
    """Centralized sound and music management"""
    
    def __init__(self):
        """Initialize sound manager"""
        self.enabled = True
        self.music_enabled = True
        self.sfx_enabled = True
        
        # Volume settings (0.0 to 1.0)
        self.music_volume = 0.5
        self.sfx_volume = 0.7
        
        # Sound effects dictionary
        self.sounds: Dict[str, pygame.mixer.Sound] = {}
        
        # Currently playing channels
        self.channels: Dict[str, pygame.mixer.Channel] = {}
        
        # Initialize pygame mixer if not already done
        if not pygame.mixer.get_init():
            pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)
        
        # Load sound effects
        self._load_sounds()
    
    def _load_sounds(self):
        """Load all sound effects"""
        # Define sound files to load
        sound_files = {
            'shoot': 'shoot.wav',
            'explosion': 'explosion.wav',
            'hit': 'hit.wav',
            'player_move': 'tank_move.wav',
            'enemy_destroyed': 'enemy_destroyed.wav',
            'game_over': 'game_over.wav',
            'level_complete': 'level_complete.wav',
            'menu_select': 'menu_select.wav',
            'power_up': 'power_up.wav'
        }
        
        # Get procedural sound generator for fallback
        sound_gen = get_procedural_sound_generator()
        
        for sound_name, filename in sound_files.items():
            sound_path = os.path.join(SOUNDS_DIR, filename)
            
            # Try to load sound file first
            if os.path.exists(sound_path):
                try:
                    sound = pygame.mixer.Sound(sound_path)
                    sound.set_volume(self.sfx_volume)
                    self.sounds[sound_name] = sound
                    print(f"Loaded sound: {sound_name} from file")
                except pygame.error as e:
                    print(f"Warning: Could not load sound {sound_name}: {e}")
                    self._load_procedural_sound(sound_name, sound_gen)
            else:
                # Use procedural sound as fallback
                self._load_procedural_sound(sound_name, sound_gen)
    
    def _load_procedural_sound(self, sound_name: str, sound_gen):
        """Load procedurally generated sound"""
        try:
            if sound_name == 'shoot':
                sound = sound_gen.generate_shoot_sound()
            elif sound_name == 'explosion':
                sound = sound_gen.generate_explosion_sound()
            elif sound_name == 'hit':
                sound = sound_gen.generate_hit_sound()
            elif sound_name == 'enemy_destroyed':
                sound = sound_gen.generate_enemy_destroyed_sound()
            elif sound_name == 'game_over':
                sound = sound_gen.generate_game_over_sound()
            elif sound_name == 'level_complete':
                sound = sound_gen.generate_level_complete_sound()
            elif sound_name == 'menu_select':
                sound = sound_gen.generate_menu_select_sound()
            elif sound_name == 'power_up':
                sound = sound_gen.generate_power_up_sound()
            else:
                # Default fallback
                sound = None
            
            if sound:
                sound.set_volume(self.sfx_volume)
                self.sounds[sound_name] = sound
                print(f"Generated procedural sound: {sound_name}")
        except Exception as e:
            print(f"Warning: Could not generate procedural sound {sound_name}: {e}")
            self.sounds[sound_name] = None
    
    def play_sound(self, sound_name: str, loops: int = 0, fade_ms: int = 0) -> Optional[pygame.mixer.Channel]:
        """
        Play a sound effect
        
        Args:
            sound_name: Name of sound to play
            loops: Number of times to loop (-1 for infinite)
            fade_ms: Fade in time in milliseconds
            
        Returns:
            Channel object if sound played, None otherwise
        """
        if not self.enabled or not self.sfx_enabled:
            return None
        
        sound = self.sounds.get(sound_name)
        if sound is None:
            return None
        
        try:
            if fade_ms > 0:
                channel = sound.play(loops=loops, fade_ms=fade_ms)
            else:
                channel = sound.play(loops=loops)
            
            if channel:
                self.channels[sound_name] = channel
            
            return channel
        except pygame.error as e:
            print(f"Error playing sound {sound_name}: {e}")
            return None
    
    def stop_sound(self, sound_name: str, fade_ms: int = 0):
        """
        Stop a playing sound
        
        Args:
            sound_name: Name of sound to stop
            fade_ms: Fade out time in milliseconds
        """
        channel = self.channels.get(sound_name)
        if channel and channel.get_busy():
            if fade_ms > 0:
                channel.fadeout(fade_ms)
            else:
                channel.stop()
    
    def play_music(self, music_file: str, loops: int = -1, fade_ms: int = 0):
        """
        Play background music
        
        Args:
            music_file: Path to music file
            loops: Number of times to loop (-1 for infinite)
            fade_ms: Fade in time in milliseconds
        """
        if not self.enabled or not self.music_enabled:
            return
        
        music_path = os.path.join(SOUNDS_DIR, music_file)
        
        # Try to load music file
        if os.path.exists(music_path):
            try:
                pygame.mixer.music.load(music_path)
                pygame.mixer.music.set_volume(self.music_volume)
                
                if fade_ms > 0:
                    pygame.mixer.music.play(loops=loops, fade_ms=fade_ms)
                else:
                    pygame.mixer.music.play(loops=loops)
                print(f"Playing music: {music_file}")
                return
            except pygame.error as e:
                print(f"Error loading music file: {e}")
        
        # Fallback to procedural music
        print(f"Music file not found: {music_path}, generating procedural music")
        self._play_procedural_music(music_file, loops)
    
    def _play_procedural_music(self, music_file: str, loops: int):
        """Play procedurally generated music"""
        try:
            sound_gen = get_procedural_sound_generator()
            
            # Generate appropriate music based on filename
            if 'menu' in music_file.lower():
                music_sound = sound_gen.generate_menu_music(duration=10.0)
            elif 'game' in music_file.lower():
                music_sound = sound_gen.generate_game_music(duration=10.0)
            else:
                music_sound = sound_gen.generate_menu_music(duration=10.0)
            
            # Play the generated sound on a channel with looping
            if music_sound:
                music_sound.set_volume(self.music_volume)
                # Use a dedicated channel for music
                music_channel = pygame.mixer.Channel(0)
                music_channel.play(music_sound, loops=loops)
                self.channels['background_music'] = music_channel
                print(f"Playing procedural music (looping)")
        except Exception as e:
            print(f"Error generating procedural music: {e}")
    
    def stop_music(self, fade_ms: int = 1000):
        """
        Stop background music
        
        Args:
            fade_ms: Fade out time in milliseconds
        """
        # Stop pygame.mixer.music
        if fade_ms > 0:
            pygame.mixer.music.fadeout(fade_ms)
        else:
            pygame.mixer.music.stop()
        
        # Also stop procedural music channel if it exists
        if 'background_music' in self.channels:
            channel = self.channels['background_music']
            if channel and channel.get_busy():
                if fade_ms > 0:
                    channel.fadeout(fade_ms)
                else:
                    channel.stop()
            del self.channels['background_music']
    
    def pause_music(self):
        """Pause background music"""
        pygame.mixer.music.pause()
        
        # Also pause procedural music channel if it exists
        if 'background_music' in self.channels:
            channel = self.channels['background_music']
            if channel and channel.get_busy():
                channel.pause()
    
    def unpause_music(self):
        """Unpause background music"""
        pygame.mixer.music.unpause()
        
        # Also unpause procedural music channel if it exists
        if 'background_music' in self.channels:
            channel = self.channels['background_music']
            if channel:
                channel.unpause()
    
    def set_music_volume(self, volume: float):
        """
        Set music volume
        
        Args:
            volume: Volume level (0.0 to 1.0)
        """
        self.music_volume = max(0.0, min(1.0, volume))
        pygame.mixer.music.set_volume(self.music_volume)
    
    def set_sfx_volume(self, volume: float):
        """
        Set sound effects volume
        
        Args:
            volume: Volume level (0.0 to 1.0)
        """
        self.sfx_volume = max(0.0, min(1.0, volume))
        
        # Update all loaded sounds
        for sound in self.sounds.values():
            if sound:
                sound.set_volume(self.sfx_volume)
    
    def toggle_music(self):
        """Toggle background music on/off"""
        self.music_enabled = not self.music_enabled
        
        if not self.music_enabled:
            self.stop_music()
    
    def toggle_sfx(self):
        """Toggle sound effects on/off"""
        self.sfx_enabled = not self.sfx_enabled
    
    def toggle_all_sound(self):
        """Toggle all sound on/off"""
        self.enabled = not self.enabled
        
        if not self.enabled:
            self.stop_music()
            self.stop_all_sounds()
    
    def stop_all_sounds(self):
        """Stop all playing sound effects"""
        for channel in self.channels.values():
            if channel and channel.get_busy():
                channel.stop()
        
        self.channels.clear()
    
    def is_music_playing(self) -> bool:
        """Check if music is currently playing"""
        return pygame.mixer.music.get_busy()
    
    def get_music_volume(self) -> float:
        """Get current music volume"""
        return self.music_volume
    
    def get_sfx_volume(self) -> float:
        """Get current sound effects volume"""
        return self.sfx_volume


# Singleton instance
_sound_manager_instance: Optional[SoundManager] = None


def get_sound_manager() -> SoundManager:
    """Get singleton sound manager instance"""
    global _sound_manager_instance
    
    if _sound_manager_instance is None:
        _sound_manager_instance = SoundManager()
    
    return _sound_manager_instance
